package org.zturn.exhibition.util;
 
import cn.hutool.core.codec.Base64Encoder;
 
import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.*;
 
/**
 * @ClassName : RSAUtil
 * @Description : RSA加密工具类
 * @Author : AD
// 编码
String originalInput = "Hello World";
String encodedString = Base64.getEncoder().encodeToString(originalInput.getBytes());
System.out.println(encodedString);  // SGVsbG8gV29ybGQ=

// 解码
byte[] decodedBytes = Base64.getDecoder().decode(encodedString);
String decodedString = new String(decodedBytes);
System.out.println(decodedString);  // Hello World
 */
public class RSAUtil{
 
    /**
     * "SHA256withRSA" 是一种使用 SHA-256 哈希算法和 RSA 加密算法结合的数字签名算法。
     * 在这种算法中，数据首先会通过 SHA-256 进行哈希处理，得到一个固定长度的摘要，然后使用 RSA 私钥对这个摘要进行加密，从而生成数字签名。
     * */
    public static final String  ALGORITHM_SHA256WITHRSA = "SHA256withRSA";
 
    public static final String KEY_ALGORITHM ="RSA";
    //RSA最大加密明文大小
    public static final int MAX_ENCRYPT_BLOCK = 117;
    //RSA最大解密密文大小
    public static final int MAX_DECRYPT_BLOCK = 128;
 
    private static char[] HEXCHAR = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
 
    /**
     * Description: 公钥分段加密
     *
     * @param data 待加密源数据
     * @param publicKey 公钥（BASE64编码）
     * @param length 段长 1024长度的公钥最大取117
     * @return byte[]
    */
    public static byte[] encryptByPublicKey(byte[] data,String publicKey,int length) throws Exception
    {
        /*
        * 将BASE64编码格式 publicKey进行解码
        * */
        byte[] publicKeyByte = decryptBASE64(publicKey);
        /*
        * 使用X509EncodedKeySpec类创建了一个X.509编码的KeySpec对象，并将publicKeyByte作为参数传入。
        * 将公钥 [字符串] 解码成 [公钥对象] ，以便用于加密数据。
         * */
        X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(publicKeyByte);
        /*
        * 通过KeyFactory获取了RSA的实例
        * */
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        /*
        * 调用generatePublic方法使用之前创建的X509EncodedKeySpec对象来生成公钥。
        * */
        Key generatePublicKey = keyFactory.generatePublic(x509EncodedKeySpec);
 
        /*
        * 创建一个Cipher实例，它是用于加密或解密数据的对象。Cipher类提供了加密和解密功能，并支持许多不同的加密算法。
        * 在这里，getInstance 方法中传入了keyFactory.getAlgorithm()[获取与指定密钥工厂相关联的算法名称。]，它用于获取与指定算法关联的 Cipher 实例。
        * */
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        /*
        * 初始化 Cipher 对象。
        * 在初始化过程中，指定加密模式为 ENCRYPT_MODE，并传入了之前生成的公钥 generatePublicKey。
        * */
        cipher.init(Cipher.ENCRYPT_MODE,generatePublicKey);
 
        int inputLen = data.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        //段落起始位置
        int offSet = 0;
        byte[] cache;
        int i = 0;
        //对数据进行分段加密
        while (inputLen - offSet > 0)
        {
            if (inputLen - offSet > length) {
                cache = cipher.doFinal(data,offSet,length);
            } else {
                cache = cipher.doFinal(data,offSet,inputLen-offSet);
            }
            out.write(cache,0,cache.length);
            i++;
            offSet = i * length;
        }
        byte[] encryptDate = out.toByteArray();
        out.close();
        return encryptDate;
    }
 
 
    /**
     * Description:
     *
     * @param data 待解密数据
     * @param privateKey 私密（BUSE64编码）
     * @param length 分段解密长度 128
     * @return byte[]
    */
    public static byte[] decryptByPrivateKey(byte[] data,String privateKey,int length) throws Exception
    {
        byte[] privateKeyByte = decryptBASE64(privateKey);
        PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(privateKeyByte);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        Key generatePrivateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.DECRYPT_MODE,generatePrivateKey);
        int inputLen = data.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] cache;
        int i = 0;
        //对数据进行分段解密
        while (inputLen - offSet > 0)
        {
            if (inputLen - offSet > length)
            {
                cache = cipher.doFinal(data,offSet,length);
            } else {
                cache = cipher.doFinal(data,offSet,inputLen - offSet);
            }
            out.write(cache,0,cache.length);
            i++;
            offSet = i * length;
        }
        byte[] decryptData = out.toByteArray();
        out.close();
        return decryptData;
    }
 
 
    /**
     * Description: BASE64解码
     *
     * @param src
     * @return byte[]
    */
    public static byte[] decryptBASE64(String src)
    {
        return Base64.getDecoder().decode(src);
    }
 
    /**
     * Description: BASE64编码
     *
     * @param src
     * @return java.lang.String
    */
    public static  String encryptBASE64(byte[] src)
    {
        return Base64.getEncoder().encodeToString(src);
    }
 
 
    /**
     * Description: 从Byte[] 数组转 16进制字符串
     *
     * @param b
     * @return java.lang.String
    */
    public static  String toHexString(byte[] b)
    {
        /*
        * 每个字节都可以用两个十六进制字符来表示，因此初始化的容量是字节数组长度的两倍。
        * */
        StringBuilder sb = new StringBuilder(b.length * 2);
        for (int i = 0; i<b.length ;i++)
        {
            /*
            * 首先取字节的高四位，然后查找对应的十六进制字符，并将其追加到StringBuilder中
            * */
            sb.append(HEXCHAR[(b[i] & 0xf0) >>> 4]);
            /*
            * 取字节的低四位，找到对应的十六进制字符，并追加到StringBuilder中。
            * */
            sb.append(HEXCHAR[b[i] & 0x0f]);
        }
        return sb.toString();
    }
 
 
    /**
     * Description: 从16进制字符串转 byte[] 数组
     *
     * @param s
     * @return byte[]
    */
    public static final byte[] toBytes(String s)
    {
        byte[] bytes;
        bytes = new byte[s.length() / 2];
        for (int i = 0; i < bytes.length ; i++)
        {
            bytes[i] = (byte) Integer.parseInt(s.substring(2*i,2*i+2),16);
        }
        return bytes;
    }
 
    /**
     * Description: 判断对象是否为null
     */
    public static boolean isEmpty(Object str){
        return (str == null || "".equals(str));
    }
 
    /**
     * RSA 公钥私钥生成器
     * */
    public static  Map generateRandomToBase64Key()throws Exception{
        String KEY_ALGORITHM ="RSA";
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_ALGORITHM);
        //密钥位数
        keyPairGenerator.initialize(1024);
        //创建公钥/私钥
        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        byte[] publicKeyEncoded = publicKey.getEncoded();
        byte[] privateKeyEncoded = privateKey.getEncoded();
        HashMap<String,String> map = new HashMap<>();
        map.put("publicKey", Base64Encoder.encode(publicKeyEncoded));
        map.put("privateKey",Base64Encoder.encode(privateKeyEncoded));
        return map;
    }
 
    /**
     * Description: 根据请求参数Map集合，排号顺序Sort，组装生成对应请求中的签名参数sign
     *
     * @param map 请求参数Map集合
     * @param allowValueNull 是否允许map中的值为null； true允许：若允许为空则会出现a=&b=
     * @return java.lang.String
    */
    public static   String  generateSortSign(Map<String,String> map,boolean allowValueNull)
    {
        List<String> keys = new ArrayList<>(map.size());
        for ( String key : map.keySet() )
        {
            /*
            * 排除下列参数数据
            * 1.不允许出现空value 且 map中为null 的键值对
            * 2.参数签名内容键值对
            * */
            if ( (!allowValueNull && isEmpty(map.get(key))) || "sign".equals(key) || "signValue".equals(key) )
            {
                continue;
            }
            keys.add(key);
        }
        /*
        * sort静态方法用于按自然顺序或自定义顺序对List进行排序
        * */
        Collections.sort(keys);
        StringBuffer stringBuffer = new StringBuffer();
        boolean isFirst = true;
        for (String key : keys){
            if (isFirst){
                stringBuffer.append(key).append("=").append(map.get(key));
                isFirst = false;
                continue;
            }
            stringBuffer.append("&").append(key).append("=").append(map.get(key));
        }
        return stringBuffer.toString();
    }
 
 
    /**
     * Description: 用于生成数据的数字签名，并将签名数据转换为十六进制字符串格式返回
     *
     * @param rawDate 签名裸数据
     * @param privateKey 私钥
     * @param algorithm 签名验算算法
     * @return java.lang.String
    */
    public static  String generateSign(byte[] rawDate,String privateKey,String algorithm)throws Exception
    {
        byte[] privateKeyBytes = decryptBASE64(privateKey);
        PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        PrivateKey generatePrivate = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
        /*
        * 使用指定的签名算法algorithm，通过Signature实例获取签名对象signature。
        * */
        Signature signature = Signature.getInstance(algorithm);
        /*
        * 初始化签名对象，传入生成的私钥generatePrivate。
        * */
        signature.initSign(generatePrivate);
        /*
        * 将要签名的裸数据rawData传入签名对象。
        * */
        signature.update(rawDate);
        /*
        * 生成签名数据sign
        * */
        byte[] sign = signature.sign();
        return toHexString(sign);
    }
 
 
    /**
     * Description: 验证签名
     *
     * @param data 请求数据
     * @param publicKey 公钥
     * @param sign 签名数据
     * @param algorithm 签名验算算法
     * @return boolean
    */
    public static boolean verify(byte[] data,String publicKey,String sign,String algorithm)throws Exception
    {
        byte[] publicKeyBytes = decryptBASE64(publicKey);
        X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(publicKeyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        PublicKey generatePublicKey = keyFactory.generatePublic(x509EncodedKeySpec);
        Signature signature = Signature.getInstance(algorithm);
        signature.initVerify(generatePublicKey);
        signature.update(data);
        return signature.verify( toBytes(sign) );
    }
}